Telegram Group & Telegram Channel
🐍 Задача с подвохом: Декораторы и мутабельные ловушки

Условие:

Что выведет следующий код и почему?


def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)


Вопрос:
Что будет выведено? Где здесь двойной подвох?

🔍 Разбор:

На первый взгляд кажется, что:

1. add_to_list(1) вернёт [1]
2. add_to_list(2) вернёт [2]
3. add_to_list(1) снова вызовет функцию (или достанет из кэша)

Но тут два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize кэширует результат по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если кэш сработает, вы получите тот же объект списка, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`
- `res2 = add_to_list(2)` → функция вызвана снова (новый аргумент), список становится `[1, 2]`
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")` и вернётся **ссылку на тот же изменённый список**

🔢 **Вывод:**

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все результаты указывают на один и тот же изменённый список.

💥 **Почему это важно:**

1️⃣ **Изменяемые аргументы по умолчанию** сохраняются между вызовами
2️⃣ **Кэширование мутабельных объектов** может привести к неожиданным результатам: при возврате списка вы возвращаете не "результат на момент вычисления", а ссылку на объект, который может измениться позже

🛡️ **Как исправить:**

1️⃣ Использовать `lst=None` и инициализировать внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать мутабельные объекты, лучше возвращать **копию**:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

**Вывод:**

Декораторы + мутабельные аргументы = ловушка даже для опытных разработчиков. Особенно, когда мутабельные объекты кэшируются и меняются за кулисами.


@pythonl



tg-me.com/pythonl/4798
Create:
Last Update:

🐍 Задача с подвохом: Декораторы и мутабельные ловушки

Условие:

Что выведет следующий код и почему?


def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)


Вопрос:
Что будет выведено? Где здесь двойной подвох?

🔍 Разбор:

На первый взгляд кажется, что:

1. add_to_list(1) вернёт [1]
2. add_to_list(2) вернёт [2]
3. add_to_list(1) снова вызовет функцию (или достанет из кэша)

Но тут два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize кэширует результат по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если кэш сработает, вы получите тот же объект списка, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`
- `res2 = add_to_list(2)` → функция вызвана снова (новый аргумент), список становится `[1, 2]`
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")` и вернётся **ссылку на тот же изменённый список**

🔢 **Вывод:**

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все результаты указывают на один и тот же изменённый список.

💥 **Почему это важно:**

1️⃣ **Изменяемые аргументы по умолчанию** сохраняются между вызовами
2️⃣ **Кэширование мутабельных объектов** может привести к неожиданным результатам: при возврате списка вы возвращаете не "результат на момент вычисления", а ссылку на объект, который может измениться позже

🛡️ **Как исправить:**

1️⃣ Использовать `lst=None` и инициализировать внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать мутабельные объекты, лучше возвращать **копию**:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

**Вывод:**

Декораторы + мутабельные аргументы = ловушка даже для опытных разработчиков. Особенно, когда мутабельные объекты кэшируются и меняются за кулисами.


@pythonl

BY Python/ django


Warning: Undefined variable $i in /var/www/tg-me/post.php on line 283

Share with your friend now:
tg-me.com/pythonl/4798

View MORE
Open in Telegram


Python django Telegram | DID YOU KNOW?

Date: |

Can I mute a Telegram group?

In recent times, Telegram has gained a lot of popularity because of the controversy over WhatsApp’s new privacy policy. In January 2021, Telegram was the most downloaded app worldwide and crossed 500 million monthly active users. And with so many active users on the app, people might get messages in bulk from a group or a channel that can be a little irritating. So to get rid of the same, you can mute groups, chats, and channels on Telegram just like WhatsApp. You can mute notifications for one hour, eight hours, or two days, or you can disable notifications forever.

Python django from ua


Telegram Python/ django
FROM USA